package org.windowkit.toolkit.widget.text;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.PopupMenu;
import java.awt.event.KeyListener;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;

import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.border.BevelBorder;
import javax.swing.event.CaretListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;

import org.windowkit.toolkit.util.LayoutUtils;


/**
 * Text Area component that can scroll and supports undo.
 * @author Eric Lundin
 */
@SuppressWarnings("serial")
public class TextArea extends JComponent implements UndoableEditListener {

	private ArrayList<String> undoTexts;

	private boolean inUndo;

	/** The text area */
	private final JTextArea textArea;

	/** The scroll pane */
	private JScrollPane scrollPane;

	/** THe panel that will hold the scroll pane with the text area inside. */
	private JPanel mainPanel;

	/** Stores whether to set the caret position to 0 when setting the text */
	private boolean scrollToBegin = false;

	/**
	 * Constructs a new TextArea.  A default model is set, the initial string
     * is null, and rows/columns are set to 0.
	 */
	public TextArea() {
		textArea = new JTextArea();
		init();
	}

    /**
     * Constructs a new TextArea with the specified text displayed.
     * A default model is created and rows/columns are set to 0.
     * @param text the text to be displayed, or null
     */
	public TextArea(final String text) {
		textArea = new JTextArea(text);
		init();
	}

    /**
     * Constructs a new empty TextArea with the specified number of
     * rows and columns.  A default model is created, and the initial
     * string is null.
     * @param rows the number of rows >= 0
     * @param columns the number of columns >= 0
     * @exception IllegalArgumentException if the rows or columns
     *  arguments are negative.
     */
	public TextArea(final int rows, final int cols) {
		textArea = new JTextArea(rows, cols);
		init();
	}

    /**
     * Constructs a new TextArea with the specified text and number
     * of rows and columns.  A default model is created.
     * @param text the text to be displayed, or null
     * @param rows the number of rows >= 0
     * @param columns the number of columns >= 0
     * @exception IllegalArgumentException if the rows or columns
     *  arguments are negative.
     */
	public TextArea(final String text, final int rows, final int cols) {
		textArea = new JTextArea(text, rows, cols);
		init();
	}

    /**
     * Constructs a new TextArea with the given document model, and defaults
     * for all of the other arguments (null, 0, 0).
     * @param doc  the model to use
     */
	public TextArea(final Document doc) {
		textArea = new JTextArea(doc);
		init();
	}

    /**
     * Constructs a new TextArea with the specified number of rows
     * and columns, and the given model.  All of the constructors
     * feed through this constructor.
     *
     * @param doc the model to use, or create a default one if null
     * @param text the text to be displayed, null if none
     * @param rows the number of rows >= 0
     * @param columns the number of columns >= 0
     * @exception IllegalArgumentException if the rows or columns
     *  arguments are negative.
     */
	public TextArea(final Document doc, final String text, final int rows, final int cols) {
		textArea = new JTextArea(doc, text, rows, cols);
		init();
	}

	/**
	 * initialize the component scroller and undo store.
	 */
	protected void init() {
		undoTexts = new ArrayList<String>();
		textArea.getDocument().addUndoableEditListener(this);
		scrollPane = new JScrollPane(textArea);
		mainPanel = new JPanel(new GridBagLayout());
		mainPanel.add(scrollPane, LayoutUtils.createConstraintsFillBoth());
		scrollPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
		setLayout(new GridBagLayout());
		add(mainPanel, LayoutUtils.createConstraintsFillBoth());
	}

	/**
	 * Undo the last edit.
	 */
	public void undo() {
		if (hasundo()) {
			inUndo = true;
			textArea.setText(undoTexts.remove(undoTexts.size() - 1));
			inUndo = false;
		}
	}

	/**
	 * Is there anything in the undo store.
	 * @return
	 */
	public boolean hasundo() {
		return undoTexts.size() > 0 ? true : false;
	}

	/**
	 * Replace the selection with the text.
	 * @param text the text to replace with
	 * @param undoable
	 */
	public void replaceSelection(final String text, final boolean undoable) {
		if (undoable) {
			undoTexts.add(textArea.getText());
		}
		textArea.replaceSelection(text);
	}

	/**
	 * {@inheritDoc}
	 */
	public void undoableEditHappened(final UndoableEditEvent e) {
		if (inUndo
				|| (undoTexts.indexOf(textArea.getText()) == undoTexts.size() - 1)) {
			return;
		}
		undoTexts.add(textArea.getText());
	}

	/**
	 * Set the maximum amount of characters allowed by this text field.
	 * @param length the maximum number of characters the user is allowed to enter.
	 */
	public void setMaxLength(final int length) {
		textArea.setDocument(new LimitedSizeDocument(length));
		textArea.getDocument().addUndoableEditListener(this);
	}

	/**
	 * Limits the amount of characters the user may enter in the text field.
	 */
	class LimitedSizeDocument extends PlainDocument {
		private final int size;

		/**
		 * Constructor.
		 * @param size The maximum number of characters the user may enter.
		 */
		public LimitedSizeDocument(final int size) {
			this.size = size;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void insertString(final int offs, final String str, final AttributeSet a)
				throws BadLocationException {
			int spaceLeft;
			if (str == null || this.getLength() >= size) {
				return;
			}
			spaceLeft = size - (this.getLength());
			if (spaceLeft > str.length()) {
				spaceLeft = str.length();
			}
			super.insertString(offs, str.substring(0, spaceLeft), a);
		}
	}

	/**
	 * Clear the text.
	 */
	public void clearValue() {
		textArea.setText("");
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
    public void add(final PopupMenu popup) {
		textArea.add(popup);
	}

	/**
	 * Add a caret listener.
	 * @param listener the listener to add
	 */
	public void addCaretListener(final CaretListener listener) {
		textArea.addCaretListener(listener);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
    public void addKeyListener(final KeyListener l) {
		textArea.addKeyListener(l);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
    public void addMouseWheelListener(final MouseWheelListener l) {
		textArea.addMouseWheelListener(l);
	}

	/**
	 * Append the text.
	 * @param str string to append
	 */
	public void append(final String str) {
		textArea.append(str);
	}

	/**
	 * Copy the text.
	 */
	public void copy() {
		textArea.copy();
	}

	/**
	 * Cut the text.
	 */
	public void cut() {
		textArea.cut();
	}

	/**
	 * Get the text area's caret.
	 * @return
	 */
	public Caret getCaret() {
		return textArea.getCaret();
	}

	/**
	 * Get the color of the caret.
	 * @return
	 */
	public Color getCaretColor() {
		return textArea.getCaretColor();
	}

	/**
	 * Get the caret listeners.
	 * @return caret listeners
	 */
	public CaretListener[] getCaretListeners() {
		return textArea.getCaretListeners();
	}

	/**
	 * @return get the caret position
	 */
	public int getCaretPosition() {
		return textArea.getCaretPosition();
	}

	/**
	 * @return get the document
	 */
	public Document getDocument() {
		return textArea.getDocument();
	}

	/**
	 * Get the Font for the underlying text area.
	 * @return the font
	 */
	@Override
    public Font getFont() {
		return textArea.getFont();
	}

	/**
	 * Get the line count for the underlying text area.
	 * @return the line count
	 */
	public int getLineCount() {
		return textArea.getLineCount();
	}

	/**
	 * Get the line end offset for the underlying text area.
	 * @param line
	 * @return
	 * @throws BadLocationException
	 */
	public int getLineEndOffset(final int line) throws BadLocationException {
		return textArea.getLineEndOffset(line);
	}

	public int getLineOfOffset(final int offset) throws BadLocationException {
		return textArea.getLineOfOffset(offset);
	}

	public int getLineStartOffset(final int line) throws BadLocationException {
		return textArea.getLineStartOffset(line);
	}

	public boolean getLineWrap() {
		return textArea.getLineWrap();
	}

	public Insets getMargin() {
		return textArea.getMargin();
	}

	public int getRows() {
		return textArea.getRows();
	}

	public String getSelectedText() {
		return textArea.getSelectedText();
	}

	public int getSelectionEnd() {
		return textArea.getSelectionEnd();
	}

	public int getSelectionStart() {
		return textArea.getSelectionStart();
	}

	public int getTabSize() {
		return textArea.getTabSize();
	}

	public String getText() {
		return textArea.getText();
	}

	public String getText(final int offs, final int len) throws BadLocationException {
		return textArea.getText(offs, len);
	}

	public boolean isEditable() {
		return textArea.isEditable();
	}

	public void moveCaretPosition(final int pos) {
		textArea.moveCaretPosition(pos);
	}

	public void paste() {
		textArea.paste();
	}

	public void removeCaretListener(final CaretListener listener) {
		textArea.removeCaretListener(listener);
	}

	public void replaceRange(final String str, final int start, final int end) {
		textArea.replaceRange(str, start, end);
	}

	public void replaceSelection(final String content) {
		textArea.replaceSelection(content);
	}

	public void select(final int selectionStart, final int selectionEnd) {
		textArea.select(selectionStart, selectionEnd);
	}

	public void selectAll() {
		textArea.selectAll();
	}

	public void setCaret(final Caret c) {
		textArea.setCaret(c);
	}

	public void setCaretColor(final Color c) {
		textArea.setCaretColor(c);
	}

	public void setCaretPosition(final int position) {
		textArea.setCaretPosition(position);
	}

	public void setColumns(final int columns) {
		textArea.setColumns(columns);
	}

	public void setDocument(final Document doc) {
		textArea.setDocument(doc);
	}

	@Override
    public void setFont(final Font f) {
		textArea.setFont(f);
	}

	public void setLineWrap(final boolean wrap) {
		textArea.setLineWrap(wrap);
	}

	public void setMargin(final Insets m) {
		textArea.setMargin(m);
	}

	/**
	 * Sets whether the user can edit the text area.
	 *
	 * @param editable
	 *            True to allow editing. False otherwise.
	 */
	public void setEditable(final boolean editable) {
		textArea.setEditable(editable);
		if (!editable) {
			textArea.setBackground(UIManager
					.getColor("TextField.disabledBackground"));
			textArea.setForeground(UIManager
					.getColor("TextField.disabledForeground"));
		}
	}

	public void setRows(final int rows) {
		textArea.setRows(rows);
	}

	public void setSelectionEnd(final int selectionEnd) {
		textArea.setSelectionEnd(selectionEnd);
	}

	public void setSelectionStart(final int selectionStart) {
		textArea.setSelectionStart(selectionStart);
	}

	public void setTabSize(final int size) {
		textArea.setTabSize(size);
	}

	public void setText(final String t) {
		textArea.setText(t);
		if (scrollToBegin) {
			textArea.setCaretPosition(0);
		}
	}

	/**
	 * Sets whether to move the view back to the beginning of the text after
	 * setting the text.
	 *
	 * @param scroll
	 *            True to move the caret to the beginning of the text after
	 *            setting the text.
	 */
	public void setScrollToBeginningOnSet(final boolean scroll) {
		scrollToBegin = scroll;
	}

	public void setWrapStyleWord(final boolean word) {
		textArea.setWrapStyleWord(word);
	}

	public void setCorner(final String key, final Component corner) {
		scrollPane.setCorner(key, corner);
	}

	public void setHorizontalScrollBarPolicy(final int policy) {
		scrollPane.setHorizontalScrollBarPolicy(policy);
	}

	public void setVerticalScrollBarPolicy(final int policy) {
		scrollPane.setVerticalScrollBarPolicy(policy);
	}

	public void setWheelScrollingEnabled(final boolean handleWheel) {
		scrollPane.setWheelScrollingEnabled(handleWheel);
	}

    @Override
    public void requestFocus() {
        textArea.requestFocus();
    }

}
